/*
 * Copyright Hundsun Technologies Inc. All Rights Reserved.
 *
 * @author wangyuan24333
 */

package txpool

import (
	"context"
	"encoding/hex"
	"errors"
	"time"

	"hundsun.com/hsl/hschain/common/collections/listmap"

	"hundsun.com/hsl/hschain/p2p/common/p2perror"

	lru "github.com/hashicorp/golang-lru"
	logger "hundsun.com/hsl/hschain/common/log"
	"hundsun.com/hsl/hschain/common/log/logging"
	"hundsun.com/hsl/hschain/common/util/pb"
	"hundsun.com/hsl/hschain/p2p/common/constant"
	"hundsun.com/hsl/hschain/protos"
	protosledger "hundsun.com/hsl/hschain/protos/ledger"
)

//Service ...
type Service struct {
	txCache      *listmap.ListMap
	preRemoveLru *lru.Cache
	ctx          context.Context
	logging.Log
}

//判断交易是否有效
func (t *Service) txVerify(tx *protosledger.Transaction) bool {
	if tx == nil {
		return false
	}
	timeLimit := pb.GetGenesisTransactionPayload().GetTimeLimit()

	minTxValidTime := tx.CreateTime - 1000*int64(timeLimit.MaxTimeIntervalError)
	maxTxValidTime := tx.CreateTime + 1000*int64(timeLimit.MaxTimeIntervalError) + 1000*int64(tx.TTL)
	if time.Now().Unix()*1000 < minTxValidTime || time.Now().Unix()*1000 > maxTxValidTime {
		return false
	}
	return true
}

//NewTxPool 新建交易池
func NewTxPool(ctx context.Context, log logging.Log) *Service {
	tempLru, _ := lru.New(constant.TxPoolBufSize)
	txPoolService := &Service{
		txCache:      listmap.New(),
		preRemoveLru: tempLru,
		Log:          log,
		ctx:          ctx,
	}
	go txPoolService.refreshTx()
	return txPoolService
}

func (t *Service) refreshTx() {
	duration := time.Second * 5
	ticker := time.NewTicker(duration)
	for {
		select {
		case <-t.ctx.Done():
			logger.Infof("tx refresher closed")
			return
		case <-ticker.C:
			newListMap := listmap.New()
			t.txCache.Walk(func(value interface{}) bool {
				tx := value.(*protosledger.Transaction)
				if t.txVerify(tx) {
					newListMap.Push(hex.EncodeToString(tx.Hash()), tx)
					return true
				}
				return true
			})
			t.txCache = newListMap
		}
	}
}

func (t *Service) cachePutTx(tx *protosledger.Transaction) error {
	if !t.txVerify(tx) {
		return nil
	}
	if t.txCache.Size() > constant.TxPoolBufSize {
		return errors.New("tx pool full")
	}
	t.txCache.Push(hex.EncodeToString(tx.Hash()), tx)
	return nil
}

func (t *Service) cacheGetTx(hash string) (*protosledger.Transaction, error) {
	tx, err := t.txCache.GetItem(hash)
	if err != nil {
		return nil, err
	}
	t.txCache.Remove(hash)
	return tx.(*protosledger.Transaction), err
}

func (t *Service) cacheGetTxs(num int) ([]*protosledger.Transaction, error) {
	var result []*protosledger.Transaction
	if num <= 0 {
		return result, nil
	}

	t.txCache.Walk(func(value interface{}) bool {
		if num == 0 {
			return false
		}
		tx := value.(*protosledger.Transaction)
		if !t.txVerify(tx) {
			return true
		}
		result = append(result, tx)
		num--
		return true
	})

	for _, item := range result {
		t.txCache.Remove(hex.EncodeToString(item.Hash()))
	}

	return result, nil
}

// AddTransaction 向交易缓冲池中添加一条交易
func (t *Service) AddTransaction(tx *protosledger.Transaction) error {
	defer t.Log.ElapsedTime("AddTransaction", time.Now())
	return t.cachePutTx(tx)
}

// AddTransactions 向交易缓冲池中添加一批交易
func (t *Service) AddTransactions(txs []*protosledger.Transaction) error {
	var length = len(txs)
	if length < 1 {
		return nil
	}
	t.Log.Debugf("push tx num:%d", length)
	defer t.Log.ElapsedTime("AddTransactions", time.Now())

	for _, tx := range txs {
		t.cachePutTx(tx)
	}
	return nil
}

//GetTransaction 获取交易
func (t *Service) GetTransaction(hash protos.Hash) (*protosledger.Transaction, error) {
	return t.cacheGetTx(hex.EncodeToString(hash))
}

// PreRemoveTransactions 从交易缓冲池中获取一批待出块的交易，获取的最大条数由limit参数决定，
// 返回的交易会被标记为“待删除”
func (t *Service) PreRemoveTransactions(limit int) ([]*protosledger.Transaction, error) {
	if limit < 1 {
		return nil, nil
	}
	var results []*protosledger.Transaction
	results, err := t.cacheGetTxs(limit)
	if err != nil {
		return nil, nil
	}

	for _, tx := range results {
		str := tx.GetXXXKey()
		t.preRemoveLru.Add(str, tx)
	}

	return results, nil
}

// getTransactions 批量获取交易缓存池指定交易（交易hash需全部命中）
func (t *Service) getTransactions(txHashes []protos.Hash) ([]*protosledger.Transaction, error) {
	var results []*protosledger.Transaction
	for _, hash := range txHashes {
		tx, err := t.cacheGetTx(hex.EncodeToString(hash))
		if err != nil {
			return nil, err
		}
		results = append(results, tx)
	}
	return results, nil
}

// PreRemoveSpecifiedTransactions 将特定交易标记为“待删除”（返回值[]int：影响记录的条数）
func (t *Service) PreRemoveSpecifiedTransactions(txHashes []protos.Hash) ([]int64, error) {
	if len(txHashes) < 1 {
		return nil, nil
	}
	var results []int64
	for _, hash := range txHashes {
		tx, _ := t.cacheGetTx(hex.EncodeToString(hash))
		if tx != nil {
			results = append(results, 1)
			str := tx.GetXXXKey()
			t.preRemoveLru.Add(str, tx)
		}
		if tx == nil {
			results = append(results, 0)
		}
	}
	return results, nil
}

// UndoPreRemoveTransactions 去掉交易上的“待删除”标记（返回值[]int：影响记录的条数）
func (t *Service) UndoPreRemoveTransactions(txHashes []protos.Hash) ([]int64, error) {
	if len(txHashes) < 1 {
		return nil, nil
	}
	var result []int64
	for _, txHash := range txHashes {
		var affect int64
		str := hex.EncodeToString(txHash)
		value, ok := t.preRemoveLru.Peek(str)
		t.preRemoveLru.Remove(str)
		if ok {
			err := t.cachePutTx(value.(*protosledger.Transaction))
			if err == nil {
				affect++
			}
		}
		result = append(result, affect)
	}
	return result, nil
}

// RemoveTransactions 从缓冲池中批量移除交易
func (t *Service) RemoveTransactions(txHashes []protos.Hash) error {
	t.Log.Debugf("txpool present num:%d", t.txCache.Size())
	if len(txHashes) < 1 {
		return nil
	}
	_, err := t.PreRemoveSpecifiedTransactions(txHashes)
	if err != nil {
		return err
	}

	for _, txHash := range txHashes {
		str := hex.EncodeToString(txHash)
		t.preRemoveLru.Remove(str)
	}
	return nil
}

//PeekTx peek transaction in list map
func (t *Service) PeekTx(txHash protos.Hash) (*protosledger.Transaction, error) {
	var item interface{}
	item, err := t.txCache.GetItem(hex.EncodeToString(txHash))
	if err != nil && err != p2perror.ErrNotFound {
		return nil, err
	}
	if item == nil {
		var ok bool
		item, ok = t.preRemoveLru.Peek(hex.EncodeToString(txHash))
		if !ok {
			return nil, p2perror.ErrNotFound
		}
	}
	tx, ok := item.(*protosledger.Transaction)
	if !ok {
		return nil, p2perror.ErrWrongType
	}
	return tx, nil
}

//PeekPendedTx peek transaction in lru
func (t *Service) PeekPendedTx(txHash protos.Hash) (*protosledger.Transaction, error) {
	value, ok := t.preRemoveLru.Peek(hex.EncodeToString(txHash))
	if !ok {
		return nil, p2perror.ErrNotFound
	}
	tx, ok := value.(*protosledger.Transaction)
	if !ok {
		return nil, p2perror.ErrWrongType
	}
	return tx, nil
}

//TxPoolNum query number of transactions in list map
func (t *Service) TxPoolNum() int {
	return t.txCache.Size()
}

//TxPoolHashes query hashes of transactions in list map
func (t *Service) TxPoolHashes(num uint) []string {
	var hashes []string
	t.txCache.Walk(func(value interface{}) bool {
		if num == 0 {
			return false
		}
		tx, ok := value.(*protosledger.Transaction)
		if !ok {
			return true
		}
		hashes = append(hashes, tx.GetXXXKey())
		num--
		return true
	})
	return hashes
}
